﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Validation;

namespace StyleMVVM.ViewModel
{
	public class DataModelViewModel<TViewModel, TModel> : BaseViewModel, ICustomTypeProvider where TModel : class
	{
		private static string supplemental = typeof(TViewModel).FullName;
		private TModel dataModel;
		private static CustomDataModelViewModelType customDataType;
		protected IValidationContext dataModelValidationContext;

		public DataModelViewModel()
		{
		}

		public DataModelViewModel(TModel dataModel)
		{
			DataModel = dataModel;
		}

		[ImportMethod]
		public void ImportDependencies(IValidationService validationService,
		                               IReflectionService reflectionService,
		                               IValidationContext validationContext)
		{
			ValidationService = validationService;
			ReflectionService = reflectionService;
			ValidationContext = validationContext;
		}

		[ActivationComplete]
		public virtual void ActivationComplete()
		{
			if (dataModel != null &&
			    dataModelValidationContext == null)
			{
				dataModelValidationContext = ValidationService.GetValidationContext(dataModel);

				dataModelValidationContext.LinkContex(ValidationContext);
			}
		}

		public Type GetCustomType()
		{
			if (customDataType == null)
			{
				customDataType = new CustomDataModelViewModelType(typeof(TViewModel), typeof(TModel));
			}

			return customDataType;
		}

		public object GetModelPropertyValue(string propertyName)
		{
			if (DataModel != null && !string.IsNullOrEmpty(propertyName))
			{
				return ReflectionService.GetPropertyValue(DataModel, propertyName);
			}

			return null;
		}

		public void SetModelPropertyValue(string propertyName, object newValue)
		{
			if (DataModel != null && !string.IsNullOrEmpty(propertyName))
			{
				ReflectionService.SetPropertyValue(DataModel, propertyName, newValue);
			}
		}

		protected IReflectionService ReflectionService { get; private set; }

		protected IValidationContext ValidationContext { get; private set; }

		protected IValidationService ValidationService { get; private set; }

		protected TModel DataModel
		{
			get { return dataModel; }
			set
			{
				if (dataModel != value)
				{
					INotifyPropertyChanged oldPropertyChanged = dataModel as INotifyPropertyChanged;

					if (oldPropertyChanged != null)
					{
						oldPropertyChanged.PropertyChanged -= DataModelOnPropertyChanged;
					}

					if (dataModelValidationContext != null)
					{
						dataModelValidationContext.UnlinkContex(ValidationContext);

						dataModelValidationContext = null;
					}

					dataModel = value;

					INotifyPropertyChanged newPropertyChanged = dataModel as INotifyPropertyChanged;

					if (newPropertyChanged != null)
					{
						newPropertyChanged.PropertyChanged += DataModelOnPropertyChanged;
					}

					foreach (PropertyInfo propertyInfo in GetCustomType().GetProperties())
					{
						try
						{
							OnPropertyChanged(propertyInfo.Name);
						}
						catch (Exception exp)
						{
							Logger.Error(
								string.Format("Exception thrown while raising property {0} on type {1} ", propertyInfo.Name, GetType().FullName),
								supplemental,
								exp);
						}
					}

					if (dataModel != null && ValidationService != null)
					{
						dataModelValidationContext = ValidationService.GetValidationContext(dataModel, true, true);

						dataModelValidationContext.LinkContex(ValidationContext);
					}
				}
			}
		}

		private void DataModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
		{
			OnPropertyChanged(propertyChangedEventArgs.PropertyName);
		}
	}
}